package uk.ac.cam.echo.app;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.List;

import uk.ac.cam.echo.ScheduleEvent;
import uk.ac.cam.echo.messages.CheckInMessage;
import uk.ac.cam.echo.messages.FriendCheckInMessage;
import uk.ac.cam.echo.messages.FriendListMessage;
import uk.ac.cam.echo.messages.FriendRequestMessage;
import uk.ac.cam.echo.messages.LocationMessage;
import uk.ac.cam.echo.messages.LocationTableMessage;
import uk.ac.cam.echo.messages.LoginMessage;
import uk.ac.cam.echo.messages.PendingFriendRequestsMessage;
import uk.ac.cam.echo.messages.Message;
import uk.ac.cam.echo.messages.RFIDMessage;
import uk.ac.cam.echo.messages.ScheduleMessage;
import uk.ac.cam.echo.messages.UserMessage;
import uk.ac.cam.echo.SafeMessageQueue;

/**
 * @author Tom Smith
 */
public class DBInt {

	private static DBInt Instance;
	/**
	 * @return The instance of DBInt.
	 */
	public static DBInt getInstance() {
		return DBInt.Instance;
	}

	public Socket getSocket() {
		return this.m_Socket;
	}

	/**
	 * Initialize and return the instance of DBInt.
	 * @param rfid The user's RFID.
	 * @param facebookID The user's facebookID
	 * @param host The host to connect to.
	 * @param port The port number to connect to.
	 * @return The instance of DBInt.
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public static DBInt getInstance(String rfid, String facebookID, String host, int port) throws UnknownHostException, IOException {
		if (DBInt.Instance == null) {
			DBInt.Instance = new DBInt(rfid, facebookID, host, port);
		}
		return DBInt.getInstance();
	}

	public static final SimpleDateFormat DateFormat = new SimpleDateFormat("[dd/MM/yy HH:mm:ss]");
	public static final SimpleDateFormat ShortDateFormat = new SimpleDateFormat("[HH:mm:ss]");
	
	public static String USER_RFID = null;

	private final String m_FacebookID;
	private final String m_RFID;

	private final SafeMessageQueue<Message> m_SafeMessageQueue = new SafeMessageQueue<Message>();

	private final Socket m_Socket;

	private final Reader m_Reader;
	private final Writer m_Writer;

	/**
	 * Initialize the class with the user's RFID, host, and port number to connect to.
	 * @param rfid The user's RFID.
	 * @param facebookID The user's facebookID
	 * @param host The host to connect to.
	 * @param port The port number to connect to.
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	private DBInt(String rfid, String facebookID, String host, int port) throws UnknownHostException, IOException {
		this.m_RFID = rfid;
		this.m_FacebookID = facebookID;
		this.m_Socket = new Socket(host, port);

		this.m_Reader = new Reader(this.m_Socket);
		Thread readThread = new Thread(this.m_Reader);
		readThread.setDaemon(true);
		readThread.start();

		this.m_Writer = new Writer(this.m_Socket, this.m_SafeMessageQueue);
		Thread writeThread = new Thread(this.m_Writer);
		writeThread.setDaemon(true);
		writeThread.start();

		sendMessage(new LoginMessage(this.m_RFID, this.m_FacebookID));
	}

	/**
	 * Sends or accepts a friend request.
	 * @param rfid The friend's RFID.
	 * @throws IOException 
	 */
	public void addFriend(String friendRFID) throws IOException {
		sendMessage(new FriendRequestMessage(this.m_RFID, friendRFID));
	}

	/**
	 * Get a list of friends
	 * @return The list of UserMessages of your friends.
	 * @throws IOException
	 * @throws InterruptedException 
	 */
	public synchronized List<UserMessage> getFriendList() throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new FriendListMessage(this.m_RFID));

		return this.m_Reader.getFriendList();
	}

	/**
	 * Get friend check ins.
	 * @return The list of CheckInMessagess of your friends
	 * @throws IOException
	 * @throws InterruptedException 
	 */
	public synchronized List<CheckInMessage> getFriendCheckIns() throws IOException, InterruptedException {
		return getFriendCheckIns(null);
	}

	/**
	 * Get friend check ins.
	 * @param friendRFID the RFID of the friend to get check ins for.
	 * @return The list of CheckInMessages of your friends
	 * @throws IOException
	 * @throws InterruptedException 
	 */
	public synchronized List<CheckInMessage> getFriendCheckIns(String friendRFID) throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new FriendCheckInMessage(this.m_RFID, friendRFID));

		return this.m_Reader.getFriendCheckIns(friendRFID);
	}

	/**
	 * Get a list of pending friend requests.
	 * @return The list of UserMessages of the friends to add.
	 * @throws IOException
	 * @throws InterruptedException 
	 */
	public synchronized List<UserMessage> getFriendRequests() throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new PendingFriendRequestsMessage(this.m_RFID));

		return this.m_Reader.getFriendRequests();
	}

	public synchronized List<LocationMessage> getLocations() throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new LocationTableMessage(this.m_RFID));

		return this.m_Reader.getLocations();
	}

	/**
	 * Gets the schedule of events in the future.
	 * @return A list of the events.
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public synchronized List<ScheduleEvent> getSchedule() throws IOException, InterruptedException {
		return getSchedule(10);
	}

	/**
	 * Gets the schedule of events in the future.
	 * @param amount The number of events to get. Default = 10.
	 * @return A list of the events.
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public synchronized List<ScheduleEvent> getSchedule(int amount) throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new ScheduleMessage(this.m_RFID, amount, null));

		return this.m_Reader.getSchedule();
	}

	/**
	 * Gets the schedule of events in the future.
	 * @param amount The number of events to get. Default = 10.
	 * @param locationRFID The location RFID to get the schedule for.
	 * @return A list of the events.
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public synchronized List<ScheduleEvent> getSchedule(int amount, String locationRFID) throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new ScheduleMessage(this.m_RFID, amount, locationRFID));

		return this.m_Reader.getSchedule();
	}

	/**
	 * Gets the schedule of events in the future.
	 * @param locationRFID The location RFID to get the schedule for.
	 * @return A list of the events.
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public synchronized List<ScheduleEvent> getSchedule(String locationRFID) throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new ScheduleMessage(this.m_RFID, 10, locationRFID));

		return this.m_Reader.getSchedule();
	}

	public synchronized Message scanRFID(String rfid) throws IOException, InterruptedException {
		if (this.m_Reader == null) return null;

		sendMessage(new RFIDMessage(rfid));

		return this.m_Reader.getMessage();
	}

	/**
	 * Puts a message on the queue to send to the client.
	 * @param m The message to be sent.
	 * @throws IOException
	 */
	private void sendMessage(Message m) throws IOException {
		this.m_SafeMessageQueue.put(m);
	}

}
